/**
 *  TodayPluginWnd.cpp
 *
 *  Copyright (C) 2008  David Andrs <pda@jasnapaka.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "stdafx.h"
#include "mCountdown.h"
#include "TodayPluginWnd.h"

#include "todaycmn.h"
#include "..\share\Config.h"

#include "..\share\defs.h"
#include "..\share\reg.h"
//#include "..\share\Localization.h"
//#include "..\share\Date.h"


#include "ctrls/CePropertySheet.h"
#include "GeneralPg.h"

OSVERSIONINFO OSVI = { 0 };

// draw
#define LINE_SPACING						2

///

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


UINT				WM_SH_UIMETRIC_CHANGE;

CTodayPluginWnd		TodayPluginWnd;

//
//
//

// GetVersionEx must be called in contructor
BOOL isPPC2003SE() {
	if (OSVI.dwMajorVersion > 4)
		return TRUE;
	else if (OSVI.dwMajorVersion == 4) {
		if (OSVI.dwMinorVersion >= 21)
			return TRUE;
		else
			return FALSE;
	}
	else
		return FALSE;
}

// structure to hold date
struct date {
	int month;
	int day;
	int year;
};

// part of # of days calc.
long int funct1 (int y, int m) {
	long int result;
	if ( m <= 2 )
		y -= 1;
	result = y;
	return (result);
}

long int funct2 (int m) {
	long int result;
	if ( m <= 2 )
		result = m + 13;
	else
		result = m + 1;
	return(result);
}

/* Function to calculate the number of days in dates */
long int day_count (int m, int d, int y) {
	long int number;
	number = 1461 * funct1(y, m) / 4 + 153 * funct2(m) / 5 + d;
	return (number);
}

long int MakeLinearDate(SYSTEMTIME *st) {
	return day_count(st->wMonth, st->wDay, st->wYear);
}

long int MakeLinearDateTime(SYSTEMTIME *st) {
	return (MakeLinearDate(st) * 1440) + (st->wHour * 60) + st->wMinute;
}

int DrawTextEndEllipsis(CDC *dc, const CString &strText, CRect *rc, UINT uFormat) {
	int nWidth = rc->Width();

	CRect rcTemp = *rc;
	dc->DrawText(strText, &rcTemp, uFormat | DT_CALCRECT);
	if (rcTemp.Width() > nWidth) {
		// Text doesn't fit in rect. We have to truncate it and add ellipsis to the end.
		CString strTemp;

		for (int i = strText.GetLength() - 1; i > 0; i--) {
			strTemp.Format(_T("%s..."), strText.Left(i));

			rcTemp = *rc;
			dc->DrawText(strTemp, &rcTemp, uFormat | DT_CALCRECT);
			if (rcTemp.Width() < nWidth) {
				// Gotcha!
				break;
			}
		}
		return dc->DrawText(strTemp, rc, uFormat);
	}

	return dc->DrawText(strText, rc, uFormat);
}

//////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// CTodayPluginWnd

CTodayPluginWnd::CTodayPluginWnd() {
	TapAndHoldTimer = 1;

	Selected = FALSE;
	RowHeight = CONFIG_DEFAULT_FONT_SIZE + LINE_SPACING;

	GetVersionEx(&OSVI);
}

CTodayPluginWnd::~CTodayPluginWnd() {
}


BEGIN_MESSAGE_MAP(CTodayPluginWnd, CWnd)
	//{{AFX_MSG_MAP(CTodayPluginWnd)
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_TIMER()
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
	ON_COMMAND(ID_SETTINGS, OnSettings)
END_MESSAGE_MAP()

void CTodayPluginWnd::CalcDays(SYSTEMTIME &st) {
	SYSTEMTIME stNow;
	GetLocalTime(&stNow);

	long int now = MakeLinearDate(&stNow);
	long int event = MakeLinearDate(&st);
	Days = event - now;
}

/////////////////////////////////////////////////////////////////////////////
// CTodayPluginWnd message handlers

BOOL CTodayPluginWnd::Register() {
	Unregister();

	WNDCLASS wc;
	memset(&wc, 0, sizeof(wc));

	wc.style		 = 0;
	wc.lpfnWndProc	 = (WNDPROC) ::DefWindowProc;
	wc.hInstance	 = AfxGetInstanceHandle();
	wc.hIcon		 = 0;
	wc.hCursor		 = 0;
	wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	wc.lpszClassName = CLASS_NAME;

	// register our window
    if (!AfxRegisterClass(&wc)) {
       return FALSE;
    }


	WM_SH_UIMETRIC_CHANGE = RegisterWindowMessage(SH_UIMETRIC_CHANGE);

	return TRUE;
}

BOOL CTodayPluginWnd::Unregister() {
	UnregisterClass(CLASS_NAME, AfxGetInstanceHandle());
	return TRUE;
}


void CTodayPluginWnd::LoadFonts() {
	m_fntNormal.DeleteObject();
	m_fntBold.DeleteObject();

	LONG dwFontSize;
	if (Config.FontSize == -1) {
		dwFontSize = SCALEY(CONFIG_DEFAULT_FONT_SIZE);
		SHGetUIMetrics(SHUIM_FONTSIZE_PIXEL, &dwFontSize, sizeof(dwFontSize), NULL);
	}
	else {
		dwFontSize = SCALEY(Config.FontSize);
	}

	HGDIOBJ hObj = ::GetStockObject(SYSTEM_FONT);
	LOGFONT lf;
	::GetObject(hObj, sizeof(LOGFONT), (LPVOID) &lf);
	lf.lfHeight = -dwFontSize;
	m_fntNormal.CreateFontIndirect(&lf);
	LineSpacing = (int) (0.1 * dwFontSize);
	RowHeight = dwFontSize;

	lf.lfWeight = FW_BOLD;
	m_fntBold.CreateFontIndirect(&lf);
}

BOOL CTodayPluginWnd::Create(HWND hwndParent) {
	HParentWnd = hwndParent;

	CRect rc;
	rc.right = SCALEX(240);
	BOOL ret = CreateEx(0, CLASS_NAME, WINDOW_NAME, WS_VISIBLE | WS_CHILD,
		0, 0, GetSystemMetrics(SM_CXSCREEN), 0, hwndParent, (HMENU) 0x1234);

	// load the icon
	HIcon = (HICON) ::LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, SCALEX(16), SCALEX(16), 0);

	// load the font
	LoadFonts();

	YOfs = SCALEY(Config.VOffset);

	GetLocalTime(&LastDate);
	CalcDays(Config.Event);

	return TRUE;
}

void CTodayPluginWnd::OnAction() {
}

void CTodayPluginWnd::ContextMenu(CPoint *pt) {
/*	UINT idCheck;
	CString s;

	CMenu mnuMode;
	mnuMode.CreatePopupMenu();
	s.LoadString(IDS_BRIEF); mnuMode.AppendMenu(MF_ENABLED | MF_STRING, ID_SET_MODE_BRIEF, s);
	s.LoadString(IDS_VERBOSE); mnuMode.AppendMenu(MF_ENABLED | MF_STRING, ID_SET_MODE_VERBOSE, s);

	CMenu mnuRemind;
	mnuRemind.CreatePopupMenu();
	s.LoadString(IDS_BIRTHDAYS); mnuRemind.AppendMenu(MF_ENABLED | MF_STRING, ID_SET_REMIND_BDS, s);
	s.LoadString(IDS_ANNIVERSARIES); mnuRemind.AppendMenu(MF_ENABLED | MF_STRING, ID_SET_REMIND_ANNS, s);
	s.LoadString(IDS_BDS_ANNS); mnuRemind.AppendMenu(MF_ENABLED | MF_STRING, ID_SET_REMIND_BDS_ANNS, s);

	// main menu
	CMenu mnu;
	mnu.CreatePopupMenu();
	s.LoadString(IDS_REFRESH); mnu.AppendMenu(MF_ENABLED | MF_STRING, ID_REFRESH_NOW, s);
	mnu.AppendMenu(MF_SEPARATOR);
	s.LoadString(IDS_MODE); mnu.AppendMenu(MF_ENABLED | MF_POPUP, (UINT) mnuMode.GetSafeHmenu(), s);
	s.LoadString(IDS_REMIND); mnu.AppendMenu(MF_ENABLED | MF_POPUP, (UINT) mnuRemind.GetSafeHmenu(), s);

	mnu.AppendMenu(MF_SEPARATOR);
	s.LoadString(IDS_SETTINGS); mnu.AppendMenu(MF_ENABLED | MF_STRING, ID_SETTINGS, s);

	idCheck = -1;
	switch (Config.Mode) {
		case MODE_BRIEF: idCheck = ID_SET_MODE_BRIEF; break;
		case MODE_VERBOSE: idCheck = ID_SET_MODE_VERBOSE; break;
	}
	mnu.CheckMenuRadioItem(ID_SET_MODE_BRIEF, ID_SET_MODE_VERBOSE, idCheck, MF_BYCOMMAND);

	idCheck = -1;
	switch (Config.RemindMode) {
		case REMIND_MODE_BIRTHDAYS: idCheck = ID_SET_REMIND_BDS; break;
		case REMIND_MODE_ANNIVERSARIES: idCheck = ID_SET_REMIND_ANNS; break;
		case REMIND_MODE_BIRTHDAYS | REMIND_MODE_ANNIVERSARIES: idCheck = ID_SET_REMIND_BDS_ANNS; break;
	}
	mnu.CheckMenuRadioItem(ID_SET_REMIND_BDS, ID_SET_REMIND_BDS_ANNS, idCheck, MF_BYCOMMAND);

	CPoint spt = *pt;
	ClientToScreen(&spt);
	mnu.TrackPopupMenu(TPM_CENTERALIGN, spt.x, spt.y, this, NULL);
*/
}

//
// Message Handlers
//

BOOL CTodayPluginWnd::OnEraseBkgnd(CDC* pDC) {
	TODAYDRAWWATERMARKINFO dwi;

	dwi.hwnd = GetSafeHwnd();
	dwi.hdc = pDC->GetSafeHdc();
	GetClientRect(&dwi.rc);
	::SendMessage(HParentWnd, TODAYM_DRAWWATERMARK, 0, (LPARAM) &dwi);

	return TRUE;
}

void CTodayPluginWnd::OnPaint() {
	CPaintDC dc(this); // device context for painting

	int saveDC = dc.SaveDC();

	CRect rcClient;
	GetClientRect(rcClient);

	// Get appropriate text color from shell
	COLORREF crText;

	if (Selected) {
		COLORREF clrBkgnd = ::SendMessage(HParentWnd, TODAYM_GETCOLOR, (WPARAM) TODAYCOLOR_HIGHLIGHT, NULL);
		CBrush brBkgnd(clrBkgnd);
		dc.FillRect(&rcClient, &brBkgnd);

		crText = ::SendMessage(HParentWnd, TODAYM_GETCOLOR, (WPARAM) TODAYCOLOR_HIGHLIGHTEDTEXT, NULL);
	}
	else {
		crText = ::SendMessage(HParentWnd, TODAYM_GETCOLOR, (WPARAM) TODAYCOLOR_TEXT, NULL);
	}

	// Important if you want the watermark behind the text
	dc.SetBkMode(TRANSPARENT);
	dc.SetTextColor(crText);

	// draw icon
	if (Config.DisplayIcon)
		DrawIconEx(dc.GetSafeHdc(), SCALEX(2), SCALEY(2), HIcon, SCALEX(16), SCALEY(16), 0, NULL, DI_NORMAL);

	rcClient.left += SCALEX(4);
	if (Config.DisplayIcon)
		rcClient.left += SCALEX(24);
	rcClient.top += YOfs;
//	rcClient.bottom -= YOfs;

	// draw
	if (Config.Event.wYear == 0) {
		CString str;
		str.LoadString(IDS_COUNTDOWN_NOT_SET);

		CFont *pOldFont = dc.SelectObject(&m_fntNormal);
		dc.DrawText(str, rcClient, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
		dc.SelectObject(pOldFont);
	}
	else {
		// draw # of days left
		CString str;
		if (Days < 0) {
			str.Format(IDS_NO_COUNTDOWN);
			CFont *pOldFont = dc.SelectObject(&m_fntNormal);
			DrawTextEndEllipsis(&dc, str, &rcClient, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
			dc.SelectObject(pOldFont);
		}
		else if (Days == 0) {
			str.Format(IDS_COUNTDOWN_TODAY, Config.EventName);
			CFont *pOldFont = dc.SelectObject(&m_fntBold);
			DrawTextEndEllipsis(&dc, str, &rcClient, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
			dc.SelectObject(pOldFont);
		}
		else if (Days == 1) {
			str.Format(IDS_COUNTDOWN_TOMORROW, Config.EventName);
			CFont *pOldFont = dc.SelectObject(&m_fntBold);
			DrawTextEndEllipsis(&dc, str, &rcClient, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
			dc.SelectObject(pOldFont);
		}
		else {
			CFont *pOldFont;

			CString sDays;
			sDays.Format(_T("%d"), Days);

			str.LoadString(IDS_COUNTDOWN);

			CString sEvent;
			sEvent.Format(_T("%s"), Config.EventName);

			CRect rcDays = rcClient;
			pOldFont = dc.SelectObject(&m_fntBold);
			DrawTextEndEllipsis(&dc, sDays, &rcDays, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP | DT_CALCRECT);
			dc.SelectObject(pOldFont);

			CRect rcText = rcClient;
			pOldFont = dc.SelectObject(&m_fntNormal);
			DrawTextEndEllipsis(&dc, str, &rcText, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP | DT_CALCRECT);
			dc.SelectObject(pOldFont);

			CRect rcEvent = rcClient;
			pOldFont = dc.SelectObject(&m_fntBold);
			DrawTextEndEllipsis(&dc, sEvent, &rcEvent, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP | DT_CALCRECT);
			dc.SelectObject(pOldFont);


			CRect rc = rcClient;
			rc.right = rc.left + rcDays.Width();
			pOldFont = dc.SelectObject(&m_fntBold);
			DrawTextEndEllipsis(&dc, sDays, &rc, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
			dc.SelectObject(pOldFont);

			rc.left = rc.right;
			rc.right = rc.left + rcText.Width();
			pOldFont = dc.SelectObject(&m_fntNormal);
			DrawTextEndEllipsis(&dc, str, &rc, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
			dc.SelectObject(pOldFont);

			rc.left = rc.right;
			rc.right = rcClient.right;
			pOldFont = dc.SelectObject(&m_fntBold);
			DrawTextEndEllipsis(&dc, sEvent, &rc, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOCLIP);
			dc.SelectObject(pOldFont);
		}
	}

	ValidateRect(rcClient);

	dc.RestoreDC(saveDC);
	// Do not call CWnd::OnPaint() for painting messages
}

BOOL CTodayPluginWnd::OnQueryRefreshCache(TODAYLISTITEM *ptli) {
	BOOL fRet = FALSE;

	SYSTEMTIME now;
	GetLocalTime(&now);
	if (LastDate.wDay != now.wDay || LastDate.wMonth != now.wMonth || LastDate.wYear != now.wYear) {
		// date has changed -> reload data
		CalcDays(Config.Event);
		Invalidate();
		LastDate = now;
	}

	// calc height
	CDC *pDC = GetDC();
	int saveDC = pDC->SaveDC();

	CRect rcClient;
	GetClientRect(rcClient);

	UINT nHeight = RowHeight;
	int margin = 2 * (UINT) SCALEY(Config.VOffset);
	if (Config.DisplayIcon) {
		if (nHeight + margin < (UINT) SCALEY(20)) {
			YOfs = (SCALEY(20) - nHeight) / 2;
			nHeight = (UINT) SCALEY(20);
		}
		else {
			YOfs = SCALEY(Config.VOffset);
			nHeight += margin;
		}
	}
	else {
		YOfs = SCALEY(Config.VOffset);
		nHeight += margin;
	}

	pDC->RestoreDC(saveDC);
	ReleaseDC(pDC);

	// don't show the plugin if there are no birthdays
	if (Config.ZeroHeight)
		nHeight = -1;

	if (nHeight != ptli->cyp) {
		ptli->cyp = nHeight;
		fRet = TRUE;
	}

	return fRet;
}

void CTodayPluginWnd::OnClearCache(TODAYLISTITEM *ptli) {
	InvalidateRect(NULL, TRUE);
}

void CTodayPluginWnd::OnDestroy() {
	DestroyIcon(HIcon); HIcon = NULL;
}

void CTodayPluginWnd::OnLButtonDown(UINT nFlags, CPoint point) {
	SetCapture();
	// only on VGA displays
	if (isPPC2003SE())
		Selected = TRUE;
	::PostMessage(HParentWnd, TODAYM_TOOKSELECTION, (WPARAM) GetSafeHwnd(), 0);

	LastCursorPos = point;

	KillTimer(TapAndHoldTimer);	// for sure
	SetTimer(TapAndHoldTimer, 100, NULL);

//	CWnd::OnLButtonDown(nFlags, point);
}

void CTodayPluginWnd::OnLButtonUp(UINT nFlags, CPoint point) {
	Selected = FALSE;
	ReleaseCapture();
	KillTimer(TapAndHoldTimer);
	Invalidate();

	CRect rc;
	GetClientRect(&rc);
	if (rc.PtInRect(point)) {
		// if the icon is shown and user tap it -> open config
		if (Config.DisplayIcon && (point.x >= 0 && point.x <= SCALEX(21) && point.y >= 0 && point.y <= SCALEY(21))) {
			// open config
			OpenConfig();
		}
		else
			OnAction();
	}

//	CWnd::OnLButtonUp(nFlags, point);
}

void CTodayPluginWnd::OnTimer(UINT nIDEvent) {
	if (nIDEvent == TapAndHoldTimer) {
		ReleaseCapture();

		KillTimer(TapAndHoldTimer);
		Selected = FALSE;

		// tap and hold
		SHRGINFO  shrg;
		shrg.cbSize = sizeof(shrg);
		shrg.hwndClient = GetSafeHwnd();
		shrg.ptDown.x = LastCursorPos.x;
		shrg.ptDown.y = LastCursorPos.y;
		shrg.dwFlags = SHRG_NOTIFYPARENT | SHRG_RETURNCMD;

		if (::SHRecognizeGesture(&shrg) == GN_CONTEXTMENU) {
			ContextMenu(&LastCursorPos);
		}
	}
}

LRESULT CTodayPluginWnd::OnUIMetricChange(WPARAM wParam, LPARAM lParam) {
	LoadFonts();
	InvalidateRect(NULL, TRUE);
	return 0;
}

//
// Command handlers
//

void CTodayPluginWnd::OnSettings() {
	OpenConfig();
}

LRESULT CTodayPluginWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
	if (message == WM_SH_UIMETRIC_CHANGE) {
		OnUIMetricChange(wParam, lParam);
		return 0;
	}
	else {
		switch (message) {
			case WM_TODAYCUSTOM_QUERYREFRESHCACHE:
				return OnQueryRefreshCache((TODAYLISTITEM *) wParam);

			case WM_TODAYCUSTOM_CLEARCACHE:
				OnClearCache((TODAYLISTITEM *) wParam);
				return 0;

			case WM_TODAYCUSTOM_RECEIVEDSELECTION:
//				if ((Data == NULL || Data->GetItemCount() <= 0) && Config.ZeroHeight)
				if (Config.ZeroHeight)
					// if zero height, pass the selection along to the next item
					return FALSE;
				else {
					Selected = TRUE;
					Invalidate();
					return TRUE;
				}

			case WM_TODAYCUSTOM_LOSTSELECTION:
				Selected = FALSE;
				InvalidateRect(NULL, FALSE);
				break;

			case WM_TODAYCUSTOM_USERNAVIGATION:
				return FALSE;

			case WM_TODAYCUSTOM_ACTION:
				OnAction();
				break;
		}

		return CWnd::WindowProc(message, wParam, lParam);
	}
}

void CTodayPluginWnd::OpenConfig() {
	CGeneralPg pgGeneral;

	CCePropertySheet sheet(IDS_OPTIONS);
	sheet.AddPage(&pgGeneral);
	sheet.SetMenu(IDR_MAIN);
	sheet.DoModal();

	Config.Save();

	CalcDays(Config.Event);
	Invalidate();
}

